#ifndef COMMON_HPP
#define COMMON_HPP

#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include "linear_algo.hpp"

#include <cstring>
#include <cerrno>

#ifdef _WIN32
#define MINIRENT_IMPLEMENTATION
#include "minirent.h"
#endif // just for windows platform now

#define ARENA_IMPLEMENTATION
#include "arena.h"
#define SV_IMPLEMENTATION
#include "sv.h"

// ------------------------------------
// 错误码
using Errno = int;
// 屏幕窗口大小
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define FPS 60
#define DELTA_TIME (1.0f / FPS)
#define CURSOR_OFFSET 0.13f

#define WINDOW_NAME "cpp-ded"
#define WINDOW_POS_X 200
#define WINDOW_POS_Y 200

const char *const font_file_path = "fonts/lazy.ttf"; // HACK: using lazy.ttf should be ok?

// -------------------------------------

// 交换
#define SWAP(T, a, b) \
    do                \
    {                 \
        T t = a;      \
        a = b;        \
        b = t;        \
    } while (0)
// 延迟
#define return_defer(value) \
    do                      \
    {                       \
        result = (value);   \
        goto defer;         \
    } while (0)

// 不可达
#define UNREACHABLE(...)                                                      \
do                                                                        \
{                                                                         \
    printf("%s:%d: UNREACHABLE: %s \n", __FILE__, __LINE__, __VA_ARGS__); \
    exit(1);                                                              \
} while (0)

// 未实现
#define UMIMPL(...)                                                             \
    do                                                                          \
    {                                                                           \
        printf("%s:%d: unimplemented: %s \n", __FILE__, __LINE__, __VA_ARGS__); \
        exit(1);                                                                \
    } while (0)

// 弃用
#define UNUSED(x) (void)x

#define DA_INIT_CAP 256

#define da_last(da) (assert((da)->count > 0), (da)->items[(da)->count - 1])

#define da_move(dst, src)                  \
    do                                     \
    {                                      \
        free((dst)->items);                \
        (dst)->items = (src)->items;       \
        (dst)->count = (src)->count;       \
        (dst)->capacity = (src)->capacity; \
    }                                      \
    while (0)

#define da_append(da, item)                                                                           \
    do                                                                                                \
    {                                                                                                 \
        if ((da)->count >= (da)->capacity)                                                            \
        {                                                                                             \
            (da)->capacity = (da)->capacity == 0 ? DA_INIT_CAP : (da)->capacity * 2;                  \
            (da)->items = (const char **)realloc((da)->items, (da)->capacity * sizeof(*(da)->items)); \
            assert((da)->items != NULL && "Alloc failed at da_append");                               \
        }                                                                                             \
        (da)->items[(da)->count++] = (item);                                                          \
    } while (0)

#define da_append_many(da, new_items, new_items_count)                                         \
    do                                                                                         \
    {                                                                                          \
        if ((da)->count + new_items_count > (da)->capacity)                                    \
        {                                                                                      \
            if ((da)->capacity == 0)                                                           \
            {                                                                                  \
                (da)->capacity = DA_INIT_CAP;                                                  \
            }                                                                                  \
            while ((da)->count + new_items_count > (da)->capacity)                             \
            {                                                                                  \
                (da)->capacity *= 2;                                                           \
            }                                                                                  \
            (da)->items = (char *)realloc((da)->items, (da)->capacity * sizeof(*(da)->items)); \
            assert((da)->items != NULL && "Alloc failed at da_append_many");                   \
        }                                                                                      \
        memcpy((da)->items + (da)->count, new_items, new_items_count * sizeof(*(da)->items));  \
        (da)->count += new_items_count;                                                        \
    } while (0)

// -------------------------------------------------------

char *temp_strcup(const char *s);
void temp_reset(void);

// 字符串builder
struct String_Builder
{
    char *items;
    size_t count;
    size_t capacity;
};
// ---------------------------------------------
#define SB_Fmt "%.*s"
#define SB_Arg(sb) (int)(sb).count, (sb).items

#define sb_append_buf da_append_many
#define sb_append_cstr(sb, cstr)  \
    do                            \
    {                             \
        const char *s = (cstr);   \
        size_t n = strlen(s);     \
        da_append_many(sb, s, n); \
    } while (0)
#define sb_append_null(sb) da_append_many(sb, "", 1)

#define sb_to_sv(sb) sv_from_parts((sb).items, (sb).count)
// ---------------------------------------------------

struct Files
{
    const char **items;
    size_t count;
    size_t capacity;
};

enum class File_Type
{
    FT_Regular,
    FT_Directory,
    FT_Other,
};

// ------------------------------------- expose apis
Errno type_of_file(const char *file_path, File_Type *ft);
Errno read_entire_file(const char *file_path, String_Builder *sb);
Errno write_entire_file(const char *file_path, const char *buff, size_t buff_size);
Errno read_entire_dir(const char *dir_path, Files *files);

Vec4f hex_to_vec4f(uint32_t color);

// ------------------------------------- impl the above apis private section
static Arena temp_arena = {0};

char *temp_strdup(const char *str)
{
    size_t n = strlen(str);

    char *ds = (char *)arena_alloc(&temp_arena, n + 1);
    memcpy(ds, str, n); // copy
    ds[n] = '\0';
    return ds;
}

void temp_reset(void)
{
    arena_reset(&temp_arena);
}

Errno read_entire_dir(const char *dir_path, Files *files)
{
    Errno result = 0;
    DIR *dir = NULL;

    dir = opendir(dir_path);
    if (dir == NULL)
    {
        return_defer(errno);
    }

    errno = 0;
    struct dirent *ent = readdir(dir);
    while (ent != NULL)
    {
        da_append(files, temp_strdup(ent->d_name));
        ent = readdir(dir);
    }

    if (errno != 0)
    {
        return_defer(errno);
    }

defer:
    if (dir)
        closedir(dir);
    return result;
}

Errno write_entire_file(const char *file_path, const char *buf, size_t buf_size)
{
    Errno result = 0;
    FILE *f = NULL;

    f = fopen(file_path, "wb");
    if (f == NULL)
        return_defer(errno);

    fwrite(buf, 1, buf_size, f);
    if (ferror(f))
        return_defer(errno);

defer:
    if (f)
        fclose(f);
    return result;
}

static Errno file_size(FILE *file, size_t *size)
{
    long saved = ftell(file);
    if (saved < 0)
        return errno;
    if (fseek(file, 0, SEEK_END) < 0)
        return errno;
    long result = ftell(file);
    if (result < 0)
        return errno;
    if (fseek(file, saved, SEEK_SET) < 0)
        return errno;
    *size = (size_t)result;
    return 0;
}

Errno read_entire_file(const char *file_path, String_Builder *sb)
{
    Errno result = 0;
    FILE *f = NULL;
    f = fopen(file_path, "r");
    if (f == NULL)
        return_defer(errno);

    size_t size;
    Errno err = file_size(f, &size);
    if (err != 0)
        return_defer(err);

    if (sb->capacity < size)
    {
        sb->capacity = size;
        sb->items = (char *)realloc(sb->items, sb->capacity * sizeof(*sb->items));
        assert(sb->items != NULL && "Make more RAM here");
    }

    fread(sb->items, size, 1, f);
    if (ferror(f))
        return_defer(errno);
    sb->count = size;

defer:
    if (f)
        fclose(f);
    return result;
}

Vec4f hex_to_vec4f(uint32_t color)
{
    Vec4f result;
    uint32_t r = (color >> (3 * 8)) & 0xFF;
    uint32_t g = (color >> (2 * 8)) & 0xFF;
    uint32_t b = (color >> (1 * 8)) & 0xFF;
    uint32_t a = (color >> (0 * 8)) & 0xFF;
    result.x = r / 255.0f;
    result.y = g / 255.0f;
    result.z = b / 255.0f;
    result.w = a / 255.0f;
    return result;
}

// HACK:this func is not impl
Errno type_of_file(const char *file_path, File_Type *ft)
{
#ifdef _WIN32
    printf("type of file is .txt");
    return -999;
#endif
    // TODO:  not support yet
    return 0;
}

#endif // COMMON_HPP